package xctrl

import (
	"errors"
	"gitee.com/xiedongji/antgo/core"
	"gitee.com/xiedongji/antgo/cpts/xlog"
	"gitee.com/xiedongji/antgo/cpts/xvalidate"
	"gitee.com/xiedongji/antgo/helper/xutils"
	"reflect"

	"github.com/gin-gonic/gin"
)

/* ====================================================================== *
 * 功能:对 Gin 的路由控制器 嵌套包裹一层 (兼容 Gin 默认路由控制器绑定模式)
 * 描述:主要作用是 把 参数绑定 参数验证 功能封装在一起, 省去每个控制器动作都重写一遍
 * ====================================================================== */
func GetHandlerFunc(handlerFunc interface{}) func(ctx *gin.Context) {
	/* ====================================================================== *
	 * 功能:GO 的反射对性能的影响是巨大的,
	 * 因此应尽量避免在 HandleFunc 中使用反射, 以上功能使用反射和不使用耗时相差约300倍.
	 * 所以, 部分 Type, Value 可以在注册路由时进行反射, 提前反射, 就避免了每次使用都反射.
	 * GetHandlerFunc(){} 在编译的时候只执行一次,每发送一次请求,这里的反射操作不会再执行了
	 * ====================================================================== */
	// 提前反射
	funcValue := reflect.ValueOf(handlerFunc)       // 反射出 该函数的实际引用地址 方便调用
	funcType := reflect.TypeOf(handlerFunc)         // 反射出 类型
	paramNum := reflect.TypeOf(handlerFunc).NumIn() // 传入函数 中 参数 个数

	// 函数参数 容错处理 防止少参数
	//if paramNum != 2 {
	//	funName := runtime.FuncForPC(funcValue.Pointer()).Name()
	//	xlog.TagError("控制器", funName+" 参数 必须为两个!")
	//}

	// 判断是否 Func
	if funcType.Kind() != reflect.Func {
		panic("the route handlerFunc must be a function")
	}

	// 每发送一次请求 func(ctx *gin.Context){} 中的代码会执行一次,故反射操作不宜写在其中,消耗性能
	return func(ctx *gin.Context) {
		// 只有一个参数说明是未重构的 HandlerFunc
		if paramNum == 1 {
			//funcValue.Call(vals2Arr(ctx)) // 兼容 gin 的普通路由模式
			handlerFuncWrap(ctx, funcValue, nil) // 处理包裹了一层 HandlerFunc 的函数
		} else {
			// 控制器 多参数模式
			paramType := funcType.In(1).Elem()         // 获取第二个参数的类型  例:dto.LoginReq
			handlerFuncWrap(ctx, funcValue, paramType) // 处理包裹了一层 HandlerFunc 的函数
		}
	}
}

// 执行控制器中的函数
func handlerFuncWrap(ctx *gin.Context, funcValue reflect.Value, paramsType reflect.Type) {
	ctrlParamsArr := vals2Arr(ctx)

	// 控制器 第二个参数不为空
	if paramsType != nil {
		// 根据 反射的 类型 实例化 例:实例化 dto.LoginReq{}
		paramInstance := reflect.New(paramsType).Interface() // 注意这里 返回的 指针类型
		// 绑定参数 到 struct
		err := ctx.ShouldBind(paramInstance) // 这里 paramsInstance为指针 随意 不能加 &

		//err := ctx.ShouldBindWith(paramInstance, &xvalidate.IForm{}) // 这里 paramsInstance为指针 随意 不能加 &
		//xlog.TagDebug(paramInstance, "解析参数")

		if err != nil {
			xlog.Logger(core.AppLogName, "web").Warn().Err(err).Msg("请求参数绑定失败,请检查参数类型是否对应")
		}

		// 通过验证工具 验证参数合法性
		if err := xvalidate.ValidateParam(ctx, paramInstance); err != nil {
			xutils.ResponseError(ctx, err)
			return
		}
		// 加入第二个参数
		ctrlParamsArr = vals2Arr(ctx, paramInstance)
	}

	/* ====================================================================== *
	* 功能:Value.Call()
	* 描述:模拟调用 Login(ctx *gin.Context, params *dto.LoginReq) 函数
	* 	  Call()中为传入的参数 需要一一对应
	* ====================================================================== */
	valArr := funcValue.Call(ctrlParamsArr)

	// 统一处理 http 请求 响应返回
	if len(valArr) != 2 {
		xutils.ResponseError(ctx, errors.New("控制器返回参数不合法"))
		return
	}

	// error 返回值
	if valArr[1].Interface() != nil {
		// 报错 返回第二个 错误信息
		err, ok := valArr[1].Interface().(error)
		if !ok {
			err = errors.New("request 报错 返回转换 报错")
		}
		xutils.ResponseError(ctx, err)
	} else {
		// 成功 返回第一个 类型数据
		xutils.ResponseSuccess(ctx, valArr[0].Interface())
	}
	return
}

// 把 多个值 转为 数组形式 用于 Call() 参数调用
func vals2Arr(vals ...interface{}) []reflect.Value {
	var rt []reflect.Value
	for _, val := range vals {
		rt = append(rt, reflect.ValueOf(val))
	}
	return rt
}
